/*
 * Copyright (C) 2015-2019 Alibaba Group Holding Limited
 */
#ifdef USING_CAN_FUNCTIONS

#include "hal_can.h"

#include "aos/kernel.h"
#include "fsl_flexcan.h"

#include "pin_mux.h"
#include "clock_config.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define RX_MESSAGE_BUFFER_NUM (9)
#define TX_MESSAGE_BUFFER_NUM (8)

/* To get most precise baud rate under some circumstances, users need to set
   quantum which is composed of PSEG1/PSEG2/PROPSEG. Because CAN clock prescaler
   = source clock/(baud rate * quantum), for e.g. 84M clock and 1M baud rate, the
   quantum should be .e.g 14=(6+3+1)+4, so prescaler is 6. By default, quantum
   is set to 10=(3+2+1)+4, because for most platforms e.g. 120M source clock/(1M
   baud rate * 10) is an integer. Remember users must ensure the calculated
   prescaler an integer thus to get precise baud rate. */
#define SET_CAN_QUANTUM 0
#define PSEG1 3
#define PSEG2 2
#define PROPSEG 1

/* Select 80M clock divided by USB1 PLL (480 MHz) as master flexcan clock source */
#define FLEXCAN_CLOCK_SOURCE_SELECT (2U)
/* Clock divider for master flexcan clock source */
#define FLEXCAN_CLOCK_SOURCE_DIVIDER (3U)
/* Get frequency of flexcan clock */
#define EXAMPLE_CAN_CLK_FREQ ((CLOCK_GetFreq(kCLOCK_Usb1PllClk) / 6) / (FLEXCAN_CLOCK_SOURCE_DIVIDER + 1U))
#if (defined(FSL_FEATURE_FLEXCAN_HAS_ERRATA_5829) && FSL_FEATURE_FLEXCAN_HAS_ERRATA_5829)
/* To consider the First valid MB must be used as Reserved TX MB for ERR005829
   If RX FIFO enable(RFEN bit in MCE set as 1) and RFFN in CTRL2 is set default zero, the first valid TX MB Number is 8
   If RX FIFO enable(RFEN bit in MCE set as 1) and RFFN in CTRL2 is set by other value(0x1~0xF), User should consider
   detail first valid MB number
   If RX FIFO disable(RFEN bit in MCE set as 0) , the first valid MB number is zero */
#ifdef RX_MESSAGE_BUFFER_NUM
#undef RX_MESSAGE_BUFFER_NUM
#define RX_MESSAGE_BUFFER_NUM (10)
#endif
#ifdef TX_MESSAGE_BUFFER_NUM
#undef TX_MESSAGE_BUFFER_NUM
#define TX_MESSAGE_BUFFER_NUM (9)
#endif
#endif
#ifndef DEMO_FORCE_CAN_SRC_OSC
#define DEMO_FORCE_CAN_SRC_OSC 0
#endif

#define HAL_CAN_LOG printf

typedef struct {
    uint32_t _id;
    uint32_t mask;
    uint8_t  rtr;  /* set indicates rtr */
    uint8_t  fifo; /* set indicates using fifo */
} flex_can_filter_t;

typedef struct
{
    uint8_t           inited;
    uint8_t           tx_mb_idx;
    uint8_t           rx_mb_idx;
    uint8_t           filter_cnt;
    uint8_t           tx_busy;                        /* set indicates busy */
    CAN_Type         *can_base;
    flexcan_handle_t *handle;
    flexcan_frame_t  *mb_rx_frame;                    /* allocated in can init */
    flexcan_frame_t  *fifo_rx_frame;                  /* allocated in can start */
    flexcan_frame_t  *mb_tx_frame;                    /* allocated in can start */
    aos_mutex_t       can_tx_mutex;
    aos_sem_t         can_tx_sem;
    flex_can_filter_t can_filter_list[MAX_FILTER_CNT]; /*allocated in set_can_filter*/
} rt1052_can_t;

typedef enum{
    flexcan_transfer_send,
    flexcan_transfer_mb_rcv,
    flexcan_transfer_fifo_rcv
} flexcan_transfer_action_t;

static rt1052_can_t rt1052_can[MAX_CAN_NUMBER];

static rt1052_can_t *get_local_list_from_instanse(CAN_Type *can_base)
{
    int8_t i = 0;
    rt1052_can_t *rc = NULL;
    for (i = 0; i < MAX_CAN_NUMBER; i++)
    {
        if (rt1052_can[i].can_base == can_base)
        {
            rc = &rt1052_can[i];
            break;
        }
    }
    return rc;
}

/* Get CAN Instanse & attribute from Logical Port */
can_mapping_t *can_mapping_node(const uint8_t port)
{
    int8_t i = 0;
    can_mapping_t *rc = NULL;
    for (i = 0; i < PORT_CAN_SIZE; i++)
    {
        if (can_mapping_table[i].logical_func == port)
        {
            rc = &can_mapping_table[i];
            break;
        }
    }
    return rc;
}

/* Get CAN Instanse & attribute from instanse */
static can_mapping_t* get_can_list_instanse(const CAN_Type * flexcan_instanse)
{
    int8_t i = 0;
    can_mapping_t* rc = NULL;
    for (i = 0; i < PORT_CAN_SIZE; i++)
    {
        if ((CAN_Type*)can_mapping_table[i].can_instanse == flexcan_instanse)
        {
            rc = &can_mapping_table[i];
            break;
        }
    }
    return rc;
}

static void prepare_flexcan_transfer(const flexcan_transfer_action_t type, CAN_Type *base)
{
    rt1052_can_t *node = get_local_list_from_instanse(base);
    if(NULL != node){
        switch(type)
        {
            case flexcan_transfer_send:
            break;

            case flexcan_transfer_mb_rcv:{
                flexcan_mb_transfer_t rxXfer;
                rxXfer.mbIdx = node->rx_mb_idx;
                rxXfer.frame = node->mb_rx_frame;
                FLEXCAN_TransferReceiveNonBlocking(base, node->handle, &rxXfer);
            }
            break;

            case flexcan_transfer_fifo_rcv:{
                flexcan_fifo_transfer_t rxXfer;
                rxXfer.frame = node->fifo_rx_frame;
                FLEXCAN_TransferReceiveFifoNonBlocking(base, node->handle, &rxXfer);
            }
            break;

            default:
            break;
        }
    }
}

static int32_t set_flexcan_rx_mb(CAN_Type *base)
{
    int rc = -1;
    rt1052_can_t *local_node = get_local_list_from_instanse(base);

    if(NULL != local_node){
        uint8_t i = 0;
        /* check if there is mb id filter */
        for(; i<local_node->filter_cnt; i++){
            if(local_node->can_filter_list[i].fifo==0){
                break;
            }
        }

        if(i != local_node->filter_cnt){
            /* Setup Rx Message Buffer. */
            flexcan_rx_mb_config_t mbConfig;

            /* Set Rx Masking mechanism. */
            FLEXCAN_SetRxMbGlobalMask(base, FLEXCAN_RX_MB_STD_MASK(local_node->can_filter_list[i].mask, 0, 0));
            mbConfig.format = kFLEXCAN_FrameFormatStandard;
            mbConfig.type = kFLEXCAN_FrameTypeData;
            mbConfig.id = FLEXCAN_ID_STD(local_node->can_filter_list[i]._id);
            FLEXCAN_SetRxMbConfig(base, local_node->rx_mb_idx, &mbConfig, true);
            prepare_flexcan_transfer(flexcan_transfer_mb_rcv, base);
            rc = 0;
        }
    }

    return rc;
}

/* set Rx fifo config: Filter MASK & Filter ID */
static int32_t set_flexcan_rx_fifo(CAN_Type *base)
{
    int rc = -1;
    rt1052_can_t *local_node = get_local_list_from_instanse(base);

    HAL_CAN_LOG("SET FLEX CAN RX FIFO...\n");

    if(NULL != local_node){
        uint32_t _id_list[MAX_FILTER_CNT];
        uint32_t _fifo_mask;
        uint8_t  _id_list_cnt = 0;
        uint8_t i = 0;
        /* check if there is mb id filter */
        for(; i<local_node->filter_cnt; i++){
            HAL_CAN_LOG("Filter ID 0x%x MASK 0x%x FIFO %d @ %d\n", local_node->can_filter_list[i]._id, local_node->can_filter_list[i].mask,
            local_node->can_filter_list[i].fifo, i);
            if(local_node->can_filter_list[i].fifo==1){
                _id_list[_id_list_cnt++] = local_node->can_filter_list[i]._id;
                _fifo_mask = local_node->can_filter_list[i].mask;
            }
        }

        HAL_CAN_LOG("List Cnt %d\n", _id_list_cnt);

        if(_id_list_cnt>0){
            /* Setup Rx Message Buffer. */
            flexcan_rx_fifo_config_t rxFifoCfg;

            /* Set Rx Masking mechanism. */
            FLEXCAN_SetRxFifoGlobalMask(base, FLEXCAN_RX_FIFO_STD_MASK_TYPE_A(_fifo_mask, 0, 0));
            uint32_t rx_fifo_filter[_id_list_cnt];
            for(i=0; i<_id_list_cnt; i++){
                HAL_CAN_LOG("FlexCAN set Filter ID 0x%x\n", _id_list[i]);
                rx_fifo_filter[i] = FLEXCAN_RX_FIFO_STD_FILTER_TYPE_A(_id_list[i], 0, 0);
            }

            rxFifoCfg.idFilterNum = _id_list_cnt;
            rxFifoCfg.idFilterTable = rx_fifo_filter;
            rxFifoCfg.idFilterType = kFLEXCAN_RxFifoFilterTypeA;
            rxFifoCfg.priority = kFLEXCAN_RxFifoPrioHigh;
            FLEXCAN_SetRxFifoConfig(base, &rxFifoCfg, true);
            rc = 0;
		}
    }

    return rc;
}

int32_t format_can_rx(flexcan_frame_t *raw_frame, can_frame_t *hal_frame)
{
    int32_t rc = -1;
    if(NULL != raw_frame && NULL !=hal_frame){
        uint8_t i = 0;
        hal_frame->header.id = raw_frame->id>>CAN_ID_STD_SHIFT;
        hal_frame->header.rtr = raw_frame->format;
        hal_frame->header.dlc = raw_frame->length<8?raw_frame->length:8;

        for(; i<raw_frame->length; i++){
            if(i<4){
                const uint8_t shift_bit = 8*(3-i);
                hal_frame->data[i] = ((raw_frame->dataWord0)&(0xFF<<shift_bit))>>shift_bit;
            }else{
                const uint8_t shift_bit = 8*(3-(i-4));
                hal_frame->data[i] = ((raw_frame->dataWord1)&(0xFF<<shift_bit))>>shift_bit;
            }
        }

        rc = 0;
    }

    return rc;
}

/*!
 * @brief FlexCAN Call Back function
 */
static void flexcan_callback(CAN_Type *base, flexcan_handle_t *handle, status_t status, uint32_t result, void *user_data)
{
    switch (status)
    {
    case kStatus_FLEXCAN_RxIdle:
        {
            rt1052_can_t *local_node = get_local_list_from_instanse(base);
            if(NULL != local_node && result==local_node->rx_mb_idx){
                can_mapping_t* list = get_can_list_instanse(base);
                if (list != NULL && list->isr_rx_handler != NULL) {
                    can_frame_t hal_frame;
                    int32_t get_can = format_can_rx(local_node->mb_rx_frame, &hal_frame);
                    prepare_flexcan_transfer(flexcan_transfer_mb_rcv, base);
                    if(get_can==0) {
                        list->isr_rx_handler(&hal_frame, list->isr_rx_para);
                    }
                }else{
                    prepare_flexcan_transfer(flexcan_transfer_mb_rcv, base);
                }
            }
        }
        break;
    case kStatus_FLEXCAN_RxFifoIdle:{
	    can_mapping_t* list = get_can_list_instanse(base);
        if (list != NULL && list->isr_rx_handler != NULL) {
            rt1052_can_t *local_node = get_local_list_from_instanse(base);
            if(NULL != local_node){
                can_frame_t hal_frame;
                int32_t get_can = format_can_rx( local_node->fifo_rx_frame, &hal_frame);
                prepare_flexcan_transfer(flexcan_transfer_fifo_rcv, base);
                if(0==get_can) {
                    list->isr_rx_handler(&hal_frame, list->isr_rx_para);
                }
            }else{
                //HAL_CAN_LOG("#311");
                prepare_flexcan_transfer(flexcan_transfer_fifo_rcv, base);
            }
        }else{
            //HAL_CAN_LOG("#316");
            prepare_flexcan_transfer(flexcan_transfer_fifo_rcv, base);
        }
	}
		break;
    case kStatus_FLEXCAN_TxIdle:

    /* Remote Message is send out and Message buffer changed to Receive one. */
    case kStatus_FLEXCAN_TxSwitchToRx:
    {
        rt1052_can_t *local_node = get_local_list_from_instanse(base);
        if(NULL != local_node && result==local_node->tx_mb_idx){
            local_node->tx_busy = 0;
            aos_sem_signal(&local_node->can_tx_sem);
        }else{
            //HAL_CAN_LOG("#");
        }
    }
        break;
    case kStatus_FLEXCAN_RxOverflow: {
        if(0==set_flexcan_rx_mb(base)){
            prepare_flexcan_transfer(flexcan_transfer_mb_rcv, base);
        }
    }
    break;
    default:
        break;
    }
}

/*!
 * @brief Main function &can_dev_list[0]
 */
int32_t hal_can_init(can_dev_t *can)
{
    int32_t rc = -1;
    if (NULL != can && can->port < MAX_CAN_NUMBER)
    {
        can_mapping_t *can_mapping = NULL;
        if (rt1052_can[can->port].inited == 0 && NULL != (can_mapping = can_mapping_node(can->port)))
        {
            flexcan_config_t flexcanConfig;
            uint8_t i = 0;

            rt1052_can[can->port].can_base = can_mapping->can_instanse;
            rt1052_can[can->port].tx_mb_idx = TX_MESSAGE_BUFFER_NUM;
            rt1052_can[can->port].rx_mb_idx = RX_MESSAGE_BUFFER_NUM;
            rt1052_can[can->port].handle    = (flexcan_handle_t *)aos_malloc(sizeof(flexcan_handle_t));
            rt1052_can[can->port].tx_busy   = 0;

            for(; i<MAX_FILTER_CNT; i++){
                rt1052_can[can->port].can_filter_list[i]._id = INVALID_CAN_ID;
            }

            rt1052_can[can->port].filter_cnt = 0;

            rt1052_can[can->port].mb_tx_frame = (flexcan_frame_t *)aos_malloc(sizeof(flexcan_frame_t));

            aos_mutex_new(&rt1052_can[can->port].can_tx_mutex);
            aos_sem_new(&rt1052_can[can->port].can_tx_sem, 0);

            /*Clock setting for FLEXCAN*/
            CLOCK_SetMux(kCLOCK_CanMux, FLEXCAN_CLOCK_SOURCE_SELECT);
            CLOCK_SetDiv(kCLOCK_CanDiv, FLEXCAN_CLOCK_SOURCE_DIVIDER);

            FLEXCAN_GetDefaultConfig(&flexcanConfig);
    /* Init FlexCAN module. */
#if (!defined(DEMO_FORCE_CAN_SRC_OSC)) || !DEMO_FORCE_CAN_SRC_OSC
#if (!defined(FSL_FEATURE_FLEXCAN_SUPPORT_ENGINE_CLK_SEL_REMOVE)) || !FSL_FEATURE_FLEXCAN_SUPPORT_ENGINE_CLK_SEL_REMOVE
    flexcanConfig.clkSrc = kFLEXCAN_ClkSrcPeri;
#else
#if defined(CAN_CTRL1_CLKSRC_MASK)
    if (!FSL_FEATURE_FLEXCAN_INSTANCE_SUPPORT_ENGINE_CLK_SEL_REMOVEn(can_mapping->can_instanse))
    {
        flexcanConfig.clkSrc = kFLEXCAN_ClkSrcPeri;
    }
#endif
#endif /* FSL_FEATURE_FLEXCAN_SUPPORT_ENGINE_CLK_SEL_REMOVE */
#endif /* DEMO_FORCE_CAN_SRC_OSC */
    /* If special quantum setting is needed, set the timing parameters. */
#if (defined(SET_CAN_QUANTUM) && SET_CAN_QUANTUM)
    flexcanConfig.timingConfig.phaseSeg1 = PSEG1;
    flexcanConfig.timingConfig.phaseSeg2 = PSEG2;
    flexcanConfig.timingConfig.propSeg = PROPSEG;
#if (defined(FSL_FEATURE_FLEXCAN_HAS_FLEXIBLE_DATA_RATE) && FSL_FEATURE_FLEXCAN_HAS_FLEXIBLE_DATA_RATE)
    flexcanConfig.timingConfig.fphaseSeg1 = FPSEG1;
    flexcanConfig.timingConfig.fphaseSeg2 = FPSEG2;
    flexcanConfig.timingConfig.fpropSeg = FPROPSEG;
#endif
#endif

#if (defined(USE_IMPROVED_TIMING_CONFIG) && USE_IMPROVED_TIMING_CONFIG)
            flexcan_timing_config_t timing_config;
            memset(&timing_config, 0, sizeof(flexcan_timing_config_t));

            if (FLEXCAN_CalculateImprovedTimingValues(flexcanConfig.baudRate, EXAMPLE_CAN_CLK_FREQ, &timing_config))
            {
                /* Update the improved timing configuration*/
                memcpy(&(flexcanConfig.timingConfig), &timing_config, sizeof(flexcan_timing_config_t));
            }
            else
            {
                HAL_CAN_LOG("No found Improved Timing Configuration. Just used default configuration\r\n\r\n");
            }
#endif
            /* baud rate */
            if(0 != can->config.baud_rate) {
                flexcanConfig.baudRate = can->config.baud_rate;
            }
            FLEXCAN_Init(can_mapping->can_instanse, &flexcanConfig, EXAMPLE_CAN_CLK_FREQ);

            /* Create FlexCAN handle structure and set call back function. */
            FLEXCAN_TransferCreateHandle(can_mapping->can_instanse, rt1052_can[can->port].handle, flexcan_callback, NULL);

            FLEXCAN_SetTxMbConfig(can_mapping->can_instanse, rt1052_can[can->port].tx_mb_idx, true);

            rt1052_can[can->port].inited = 1;
            rc = 0;
        }
    }
    return rc;
}

int32_t hal_can_start(can_dev_t *can)
{
    int32_t rc = -1;
    if (NULL != can && can->port < MAX_CAN_NUMBER)
    {
        if (rt1052_can[can->port].inited == 1){
            if(0==set_flexcan_rx_mb(rt1052_can[can->port].can_base)){
                rt1052_can[can->port].mb_rx_frame = (flexcan_frame_t *)aos_malloc(sizeof(flexcan_frame_t));
                prepare_flexcan_transfer(flexcan_transfer_mb_rcv, rt1052_can[can->port].can_base);
            }

            if(0==set_flexcan_rx_fifo(rt1052_can[can->port].can_base)){
                rt1052_can[can->port].fifo_rx_frame = (flexcan_frame_t *)aos_malloc(sizeof(flexcan_frame_t));
                prepare_flexcan_transfer(flexcan_transfer_fifo_rcv, rt1052_can[can->port].can_base);
            }

            rc = 0;
        }
    }
    return rc;
}

/**
 * config a CAN fliter
 *
 * @param[in]  can             the interface which should be initialised
 * @param[in]  filter_grp_cnt  bit7 set indicates if add into independent message buffer.
 * @param[in]  filter_config   point to a filter config
 *
 * @return  0 : on success, EIO : if an error occurred with any step
 */
int32_t hal_can_filter_init(can_dev_t *can, const uint8_t filter_grp_cnt, can_filter_item_t *filter_config)
{
    int32_t rc = -1;

    if(NULL != can && can->port < MAX_CAN_NUMBER && 0 != ((~CAN_FILTER_GRP_PROP)&filter_grp_cnt) && NULL != filter_config){

        uint8_t j = 0;
        for(; j<((~CAN_FILTER_GRP_PROP)&filter_grp_cnt) && rt1052_can[can->port].filter_cnt<MAX_FILTER_CNT; j++, rt1052_can[can->port].filter_cnt++){
            rt1052_can[can->port].can_filter_list[rt1052_can[can->port].filter_cnt]._id = filter_config[j].check_id;
            rt1052_can[can->port].can_filter_list[rt1052_can[can->port].filter_cnt].mask = filter_config[j].filter_mask;
            rt1052_can[can->port].can_filter_list[rt1052_can[can->port].filter_cnt].fifo = (0==(CAN_FILTER_GRP_PROP&filter_grp_cnt))?1:0;
        }
        rc = 0;
    }

    return rc;
}

int32_t hal_can_send(can_dev_t *can, const can_frameheader_t *tx_header, const void *data, const uint32_t timeout)
{
    int32_t rc = -1;
    if (NULL != can && can->port < MAX_CAN_NUMBER && NULL != tx_header)
    {
        if (rt1052_can[can->port].inited == 1)
        {
            while(0!=rt1052_can[can->port].tx_busy){
                if(aos_sem_wait(&rt1052_can[can->port].can_tx_sem, timeout) != 0) {
                    break;
                }
            }

            if(0==rt1052_can[can->port].tx_busy) {
                flexcan_mb_transfer_t tx_mb_transter;

                rt1052_can[can->port].mb_tx_frame->id = FLEXCAN_ID_STD(tx_header->id);
                rt1052_can[can->port].mb_tx_frame->format = kFLEXCAN_FrameFormatStandard;
                rt1052_can[can->port].mb_tx_frame->type = tx_header->rtr==0?kFLEXCAN_FrameTypeData:kFLEXCAN_FrameTypeRemote;
                rt1052_can[can->port].mb_tx_frame->length = tx_header->dlc<8?tx_header->dlc:8;
                if(rt1052_can[can->port].mb_tx_frame->length>0 && NULL != data){
                    uint8_t i = 0;

                    rt1052_can[can->port].mb_tx_frame->dataWord0 = 0;
                    rt1052_can[can->port].mb_tx_frame->dataWord1 = 0;
                    for(; i<rt1052_can[can->port].mb_tx_frame->length; i++){
                        if(i<4){ /* shift_bit */
                            rt1052_can[can->port].mb_tx_frame->dataWord0 |= ((uint8_t *)data)[i]<<8*(3-i);
                        }else{
                            rt1052_can[can->port].mb_tx_frame->dataWord1 |= ((uint8_t *)data)[i]<<8*(3-(i-4));
                        }
                    }
                }

                tx_mb_transter.mbIdx = rt1052_can[can->port].tx_mb_idx;

                tx_mb_transter.frame = rt1052_can[can->port].mb_tx_frame;
                rt1052_can[can->port].tx_busy = 1;
                FLEXCAN_TransferSendNonBlocking(rt1052_can[can->port].can_base, rt1052_can[can->port].handle, &tx_mb_transter);

                rc = 0;
            }
        }
    }

    return rc;
}

int32_t hal_can_recv(can_dev_t *can, can_frameheader_t *rxheader, void *data, const uint32_t timeout)
{
    int32_t rc = -1;
    can_mapping_t* can_mapping = NULL;
    if ((can != NULL) && ((can_mapping = can_mapping_node(can->port)) != NULL)) {
        if (can_mapping->customer_recv != NULL) {
            rc = can_mapping->customer_recv(rxheader, data, timeout);
        } else {/* polling mode */
            HAL_CAN_LOG("Not Support BLOCKING Mode CAN\r\n");
        }
    }
    return rc;
}


int32_t hal_can_finalize(can_dev_t *can)
{
    int32_t rc = -1;
    if (NULL != can && can->port < MAX_CAN_NUMBER) {
        if (rt1052_can[can->port].inited == 1) {
            FLEXCAN_TransferAbortReceive(rt1052_can[can->port].can_base, rt1052_can[can->port].handle, rt1052_can[can->port].rx_mb_idx);
            FLEXCAN_TransferAbortSend(rt1052_can[can->port].can_base, rt1052_can[can->port].handle, rt1052_can[can->port].tx_mb_idx);
            FLEXCAN_Deinit(rt1052_can[can->port].can_base);
            rt1052_can[can->port].inited = 0;
            rc = 0;
        }
    }
    return rc;
}
#endif /* USING_CAN_FUNCTIONS */
